package org.arl.modem.linktuner;

import jade.core.AID;
import jade.core.behaviours.SimpleBehaviour;
import jade.util.Logger;
import java.util.*;
import org.arl.modem.FAPIMessage;
import org.arl.modem.phy.Physical;

public class ParamSetter{
	
	protected SmartBoy smartboy;
	protected Logger log;
	
	protected  AID notify;	
	//Anyone who wants to subscribe to the dumb-tuner notifications can use this.
	public final static String NOTIFICATIONS = "ParamSetter-ntf";
	//List of parameters that can be tuned
	protected final static int MTYPE=0;
	protected final static int DMODE=263;
	protected final static int MPSK=261;
	protected final static int Nc=256;
	protected final static int Np=257;
	protected final static int Ns=258;
	protected final static int Nz=259;
	protected final static int PKTLEN=4;
	protected final static int FEC=2;

	protected final static int Scheme_Params[]={MTYPE,DMODE,MPSK,Nc,Np,Nz,PKTLEN,FEC};
	protected final static int Scheme_Params_Values[][]={{1,2},								//MTYPE:1=incoherent,2=coherent		
														{1,2},								//DMODE:1=time,2=freq
														{2,4},								//MPSK:2=BPSK,4=QPSK 											
														{64,128,256,512,1024},				//Nc
														{0,8,16,32,64,128,256,256,512,1024},//Np
														{0},                                //Nz
														{18,144},                           //PKTLEN
														{0,1024,65536,66560}                //FEC:0=none, 1024=conv(rate=1/3) only, 65536=golay only, 66560=both.
														};
	
	protected static int ACTIVE_SCHEME=2;
	
	public final static int TYPE_SET_SCHEME_PARAM_M=0xFF;
	public final static int TYPE_SET_SCHEME_PARAM_M_headerLen=1;
	public final static int TYPE_SEND_SCHEME_PARAM_ACK_S=0xFE;
	public final static int PARAM_SETTER_PROTOCOL_ID = 4;
	
	int currentSetSchemeParam[]={1,1,1,4,6,0,1,3};
	
	private resendControlPacketBehaviour resendSetSchemeRequest;
	public int resendDelay=4000;
	public int resendMaxTries=4;

	
	protected ParamSetter(SmartBoy smartboy, Logger log){
		this.smartboy=smartboy;
		this.log=log;
		System.out.println("ParamSetter agent is loaded");
	}
		
	protected void handleMessage(FAPIMessage msg) {
		switch (msg.getAsInteger("ParamSetterCommands",-1)){
			case SmartBoyCommands.S_INIT_COMMAND:
				setSchemeRequest(currentSetSchemeParam,true);
				break;
			case TYPE_SET_SCHEME_PARAM_M:
				setSchemeParam(msg, 1); //call with second argument=1 if you want to return an ack
				break;
			case TYPE_SEND_SCHEME_PARAM_ACK_S:
				processSendSchemeParamAck(msg);
				break;
		}
	}
	//Master sends out a request to the slave to set some scheme params 
	private boolean setSchemeRequest(int[] currentSetSchemeParam, boolean invokeResendBehaviour){
		log.fine("in setShemeRequest() method");
		byte[] setSchemeParam=new byte[currentSetSchemeParam.length];
		for(int i=0;i<currentSetSchemeParam.length;i++){
			setSchemeParam[i]=(byte)currentSetSchemeParam[i];
		}
		FAPIMessage req=smartboy.createMessage(FAPIMessage.REQUEST,smartboy.link);
		req.add("protocol",PARAM_SETTER_PROTOCOL_ID);
		byte[] rScheme=new byte[(TYPE_SET_SCHEME_PARAM_M_headerLen+setSchemeParam.length)];
		rScheme[0]=(byte)TYPE_SET_SCHEME_PARAM_M;
		System.arraycopy(setSchemeParam, 0, rScheme, TYPE_SET_SCHEME_PARAM_M_headerLen, setSchemeParam.length);
		req.setData(rScheme);
		log.fine("req.getData().length = "+req.getData().length);
		FAPIMessage reply=smartboy.request(req, 2000);
		if(reply != null && reply.getPerformative() == FAPIMessage.AGREE){
			log.fine("master has sent a request to set scheme parameters:"+rScheme[2]+"  "+rScheme[3]+"  "+rScheme[4]+"  "+rScheme[5]);
			if(invokeResendBehaviour){
				log.fine("about to instantiate resend behaviour");
				resendSetSchemeRequest=new resendControlPacketBehaviour(resendDelay, resendMaxTries, System.currentTimeMillis(), TYPE_SET_SCHEME_PARAM_M);
				smartboy.addBehaviour(resendSetSchemeRequest);
			}			
			return true;
		}
		else {
			log.warning("not successful in sending out set scheme parameter request");
			diagnose(reply);
			return false;
		}
	}

	//Sets ownself's physical layer params. Use value==1 upon receiving the setSchemeRequest to send back an ack. Use any other value
	//if you are the master and have received an ack, and just want to set your own params.   
	private void setSchemeParam(FAPIMessage msg, int value){
			log.fine("msg.getData().length = "+ msg.getData().length);
			byte[] valueArrayReceived=new byte[currentSetSchemeParam.length];
			log.fine("size of valueArrayReceived = "+valueArrayReceived.length);
			System.arraycopy(msg.getData(), TYPE_SET_SCHEME_PARAM_M_headerLen, valueArrayReceived, 0, valueArrayReceived.length);
			boolean setSchemeParamOperation=true;
			int n=0;
			while(n<valueArrayReceived.length){
				FAPIMessage reply=setSingleSchemeParam(Scheme_Params[n], Scheme_Params_Values[n][valueArrayReceived[n]], ACTIVE_SCHEME);
				if(reply!=null && reply.getPerformative()==FAPIMessage.INFORM){
					log.fine("set ownself's parameter "+reply.getAsInteger("parameter", -1)+" to value = "+reply.getAsInteger("value", -1));
				}
				else{
					setSchemeParamOperation=false;
					log.warning("failed to set scheme paramter");
				}
				n++;
			}
		log.fine("just asked the phy layer to set params");
		if (setSchemeParamOperation==false){
	    	log.warning("----seem unable to set phy params :(");
	    }
		else{
	    	if(value==1){
	    		log.fine("slave is now going to return ack");
	    		sendSchemeParamAck(msg); //send back an ack iff value==1
	    	}
		}
	}
	//sets the value for a single parameter
	private FAPIMessage setSingleSchemeParam(int param, int value, int family){
		log.fine("inside the setSingleSchemeParam method");
		FAPIMessage req=smartboy.createMessage(FAPIMessage.REQUEST, smartboy.phy, Physical.PARAM_SET);
		req.setContent(Physical.PARAM_SET);
		req.add("family", family);
		req.add("parameter", param);
		req.add("value",value);
		FAPIMessage reply=smartboy.request(req,2000);
		return reply;
	}
	private void sendSchemeParamAck(FAPIMessage msg){
		log.fine("inside the sendSchemeParamAck method");
		FAPIMessage req=smartboy.createMessage(FAPIMessage.REQUEST,smartboy.link);
		req.add("protocol",PARAM_SETTER_PROTOCOL_ID);
		byte[] rScheme=msg.getData();
		rScheme[0]=(byte)TYPE_SEND_SCHEME_PARAM_ACK_S;
		req.setData(rScheme);
		FAPIMessage reply=smartboy.request(req, 2000);
		if(reply != null && reply.getPerformative() == FAPIMessage.AGREE){
			log.fine("slave has sent an ack back to the master");
			//return true;
		}
		else {
			log.warning("slave is not successful in sending out set scheme parameter ack");
			diagnose(reply);
			//return false;
		}
	}
	private void processSendSchemeParamAck(FAPIMessage msg){
		byte[] valueArray=new byte[currentSetSchemeParam.length];
		System.arraycopy(msg.getData(), TYPE_SET_SCHEME_PARAM_M_headerLen, valueArray, 0, valueArray.length);
		byte[] currentSetSchemeParamByteArray=new byte[currentSetSchemeParam.length];
		for(int i=0;i<currentSetSchemeParam.length;i++){
			currentSetSchemeParamByteArray[i]=(byte)currentSetSchemeParam[i];
		}
		if(Arrays.equals(currentSetSchemeParamByteArray, valueArray)){
			smartboy.removeBehaviour(resendSetSchemeRequest);
			log.fine("received ack corresponding to current set scheme param request");
			setSchemeParam(msg, 0);  //remember to pass the second argument as zero if you are the master and want to send a test pkt train after setting your params. 
		}
		else{
			log.fine("recieved ack does not match current set scheme param request");
		}
	}
	//tries to resend the control packet after a delay of delay milliseconds and for upto maxTries number of tries.
	private class resendControlPacketBehaviour extends SimpleBehaviour{
		private int _delay;
		private int _ctrlPktType;
		private int _maxTries;
		private long _timeInvocation;
		private int state;
		resendControlPacketBehaviour(int delay, int maxTries, long timeInvocation, int ctrlPktType){
			log.fine("resendControlPacketBehaviour constructor invoked!");
			this._delay=delay;
			this._ctrlPktType=ctrlPktType;
			this._maxTries=maxTries;
			this._timeInvocation=timeInvocation;
			this.state=0;
		}
		public void action() {
			long t=System.currentTimeMillis()-_timeInvocation;
			if(t>=_delay*(state+1)){
				if(state==0){
					log.fine("state = 0, about to call block(), time diff = "+t);
					block(_delay);
				}else if(state>_maxTries){
					log.warning("Slave modem doesn't seem to be responding");
				}else if(state>0 && state<(_maxTries+1)){     
					log.fine("state = "+state+", about to enter switch() block");
					switch(_ctrlPktType){
						case TYPE_SET_SCHEME_PARAM_M:
							log.fine("Timer has been triggered. calling setSchemeRequest(), time diff = "+t);
							setSchemeRequest(currentSetSchemeParam, false);
							break;
					}
				}
				state++;
			}
		}
		public boolean done() {
			return (state>_maxTries);
		}
	}
	
	private void diagnose(FAPIMessage reply){
		if(reply==null)log.fine("reply==null");
		if(reply.getPerformative()==FAPIMessage.FAILURE)log.fine("perf=failiure");
		if(reply.getPerformative()==FAPIMessage.NOT_UNDERSTOOD)log.fine("perf=not-understood");
		if(reply.getPerformative()==FAPIMessage.REFUSE)log.fine("perf=refuse");
		if(reply.getPerformative()==FAPIMessage.DISCONFIRM)log.fine("perf=disconfirm");
		log.fine("exiting diagnose");
	}

}
